# -*- coding: utf-8 -*-
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
import re
import time
from itertools import islice

import exifread
import sklearn
from numpy import genfromtxt
from numpy import transpose
from sklearn.datasets import make_classification
from sklearn.cluster import Birch
from matplotlib import pyplot
from sklearn.cluster import DBSCAN
from numpy import where
from numpy import unique

import numpy
import oss2
import argparse
import os
import json
import platform
import sys
import datetime
from time import strftime
from pathlib import Path
from PIL import ImageFont, ImageDraw, Image
from time import sleep
import torch
import torch.backends.cudnn as cudnn
from typing import List
from collections import Counter

FILE = Path(__file__).resolve()
ROOT = FILE.parents[0]  # YOLOv5 root directory
if str(ROOT) not in sys.path:
    sys.path.append(str(ROOT))  # add ROOT to PATH
ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  # relative

from models.common import DetectMultiBackend
from math import radians, cos, sin, asin, sqrt
from utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadStreams
from utils.general import (LOGGER, check_file, check_img_size, check_imshow, check_requirements, colorstr, cv2,
                           increment_path, non_max_suppression, print_args, scale_coords, strip_optimizer, xyxy2xywh)
from utils.plots import Annotator, colors, save_one_box
from utils.torch_utils import select_device, time_sync

frame_x_start = int((0.104297 - 0.182812 / 2) * 2560)
frame_y_start = int((0.576389 - 0.361111 / 2) * 1440)
frame_x_end = int((0.104297 + 0.182812 / 2) * 2560)
frame_y_end = int((0.576389 + 0.361111 / 2) * 1440)

rgb = [(0, 255, 0), (0, 0, 255), (255, 0, 0), (255, 120, 0), (0, 255, 255),
       (200, 0, 255), (255, 255, 0), (75, 255, 75), (35, 216, 111), (0, 0, 0), (255, 255, 255)]


def read_line_count(file_name):
    return len(open(file_name).readlines())


def convert_gps(coord_arr):
    arr = str(coord_arr).replace('[', '').replace(']', '').split(', ')
    d = float(arr[0])
    m = float(arr[1])
    s = float(arr[2].split('/')[0]) / float(arr[2].split('/')[1])
    return float(d) + (float(m) / 60) + (float(s) / 3600)


def read_gps_txt():
    data = genfromtxt('GPS.txt', delimiter=',')
    return transpose([data[:, 1], data[:, 0], data[:, 2]])


def get_address_distance(address_img, address_txt):
    lon1, lat1, lon2, lat2 = map(radians, [address_img[0], address_img[1], address_txt[0], address_txt[1]])
    dlon = lon2 - lon1
    dlat = lat2 - lat1
    a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2
    c = 2 * asin(sqrt(a))
    r = 6371
    dis = round((c * r * 1000) / 1000, 10)
    return dis


def get_oss_bucket():
    auth = oss2.Auth('LTAI5tGPkHofe2wfSkE23csC', 'Tvw6H18pOrlaOzXYI0OcMu87XzbFwT')
    return oss2.Bucket(auth, 'oss-cn-shanghai.aliyuncs.com', 'anyibucket')


def get_now_datetime():
    now = datetime.datetime.now()
    return str(now.strftime("%Y-%m-%d %H:%M:%S"))


# 保存文件
def save_to_file(file_name, contents):
    fh = open(file_name, 'w')
    fh.write(contents)
    fh.close()


# 拼接图片铝棒数量的json文件
def assembly_number_json(number, index):
    number = str(number)
    with open('number.json', 'r') as f:
        json_data = json.load(f)
        for data in json_data:
            content = data["content"]
            re1 = r'第(.*?)堆'
            reResult = re.findall(re1, content)
            if int(str(reResult[0])) == index:
                data["value"] = number
                data["content"] = "第" + str(index) + "堆铝棒数量：" + number
        dicts = json_data
    dicts = str(dicts).replace("'", "\"")
    save_to_file("number.json", str(dicts))


def gbk_to_utf8(inFilePath, outFilePath):
    with open(inFilePath, 'rb') as f1:
        a = f1.read()
        b = a.decode('gbk', 'ignore')
        with open(outFilePath, 'w', encoding='utf8') as f2:
            f2.write(b)


def upload_json_file():
    gbk_to_utf8("number.json", "number_utf8.json")
    bucket = get_oss_bucket()
    with open("number_utf8.json", 'rb') as fileobj:
        fileobj.seek(0, os.SEEK_SET)
        bucket.put_object('datav/json/number.json', fileobj)


# 删除本地文件
def del_local_img():
    for name in os.listdir("fontimg/datav/img1"):
        os.remove("fontimg/datav/img1/" + name)
    for name in os.listdir("backimg/datav/img1"):
        os.remove("backimg/datav/img1/" + name)


def draw_x(frame, x_centre, y_centre, rgb_index):
    i = 0
    while i < 5:
        frame = cv2.rectangle(frame,
                              (int(x_centre) + i, int(y_centre) + i),
                              (int(x_centre) + i, int(y_centre) + i),
                              rgb[rgb_index], 3)
        frame = cv2.rectangle(frame,
                              (int(x_centre) - i, int(y_centre) - i),
                              (int(x_centre) - i, int(y_centre) - i),
                              rgb[rgb_index], 3)
        frame = cv2.rectangle(frame,
                              (int(x_centre) + i, int(y_centre) - i),
                              (int(x_centre) + i, int(y_centre) - i),
                              rgb[rgb_index], 3)
        frame = cv2.rectangle(frame,
                              (int(x_centre) - i, int(y_centre) + i),
                              (int(x_centre) - i, int(y_centre) + i),
                              rgb[rgb_index], 3)
        i += 1


# 改变图片
def drawing(names, nameindex):
    print("绘制")
    f = open("fontimg/datav/img1/" + names[nameindex], 'rb')
    contents = exifread.process_file(f)
    lon = convert_gps(contents['GPS GPSLongitude'].printable)
    lat = convert_gps(contents['GPS GPSLatitude'].printable)
    f.close()
    dis_min = 99
    gpses = read_gps_txt()
    gps_num = []
    for gps in gpses:
        dis = get_address_distance(gps, [lon, lat])
        if dis < dis_min:
            dis_min = dis
            gps_num = gps

    d0 = genfromtxt("txt/uav" + str(names[nameindex]) + ".txt", delimiter=' ')
    img = cv2.imread("fontimg/datav/img1/" + names[nameindex])
    frame = img
    img_width = img.shape[1]
    img_height = img.shape[0]
    txt_len = read_line_count("txt/uav" + str(names[nameindex]) + ".txt")
    wz = {}
    count = {}
    if txt_len != 1 and txt_len != 0:
        d = transpose([d0[:, 1], d0[:, 2]])
        dbscan = DBSCAN(eps=0.05, min_samples=20).fit(d)
        yhat = dbscan.labels_
        clusters = unique(yhat)
        count = Counter(dbscan.labels_)
        for cluster in clusters:
            if cluster == -1:
                continue
            rgb_index = cluster + 1
            row_ix = where(yhat == cluster)

            low_right_coord = [9999, 9999, 0, 0]

            for row_ix_child in row_ix:
                for xy_index in row_ix_child:
                    x_centre = d[xy_index, 0]
                    y_centre = d[xy_index, 1]
                    draw_x(frame, x_centre * img_width, y_centre * img_height, rgb_index)
                    if low_right_coord[0] > x_centre:
                        low_right_coord[0] = x_centre
                    if low_right_coord[1] > y_centre:
                        low_right_coord[1] = y_centre
                    if low_right_coord[2] < x_centre:
                        low_right_coord[2] = x_centre
                    if low_right_coord[3] < y_centre:
                        low_right_coord[3] = y_centre
            wz[rgb_index] = low_right_coord
        print(wz)
    fontpath = "font/simsun.ttc"
    font = ImageFont.truetype(fontpath, 128)
    # for key in count.keys():
    #     if key == -1:
    #         continue
    #     cv2.rectangle(frame, (int(wz[key + 1][0] * img_width), int(wz[key + 1][1] * img_height)),
    #                   (int(wz[key + 1][2] * img_width), int(wz[key + 1][3] * img_height)), rgb[int(key) + 1], 3)
    img_pil = Image.fromarray(frame)
    draw = ImageDraw.Draw(img_pil)
    for key in count.keys():
        if key == -1:
            txt_len = txt_len - int(count[key])
            continue
        draw.text((img_width / 5 * 3, img_height - int(key) * 128 - 400),
                  "第" + str(int(key) + 1) + "组：" + str(count[key]), font=font,
                  fill=rgb[int(key) + 1])
        # draw.text((wz[key + 1][0] * img_width, wz[key + 1][3] * img_height),
        #           "第" + str(int(key) + 1) + "组：" + str(count[key]), font=font,
        #           fill=rgb[int(key) + 1])
    # 绘制文字信息
    draw.text((100, 350), "识别铝棒数量：" + str(txt_len), font=font, fill=(255, 0, 0))
    draw.text((img_width / 5 * 3, img_height - 250), "更新时间：" + str(get_now_datetime()), font=font, fill=(0, 255, 0))
    bk_img = numpy.array(img_pil)

    # 第几张照片
    img_index = int(gps_num[2])

    cv2.imwrite("backimg/datav/img1/img" + str(img_index) + ".jpg", bk_img)
    upload_img_to_oss("backimg/datav/img1/img" + str(img_index) + ".jpg",
                      "datav/img2/img" + str(img_index) + ".jpg")
    assembly_number_json(txt_len, img_index)
    print("绘制完成")
    return str(txt_len)


# 上传图片到OSS
def upload_img_to_oss(local_img_address, oss_address):
    bucket = get_oss_bucket()
    with open(str(local_img_address), 'rb') as fileobj:
        fileobj.seek(0, os.SEEK_SET)
        bucket.put_object(str(oss_address), fileobj)


# 从OSS拿到无人机输入的图片
def get_oss():
    bl = False
    bucket = get_oss_bucket()
    download_local_save_prefix = "fontimg/"
    for b in islice(oss2.ObjectIterator(bucket, prefix="datav/img1/"), 500000):
        if b.key[-1] == "/":
            continue
        bl = True
        bucket.get_object_to_file(b.key, download_local_save_prefix + b.key)
        bucket.delete_object(b.key)
    return bl


@torch.no_grad()
def run(
        weights=ROOT / 'yolov5s.pt',  # model.pt path(s)
        source=ROOT / 'data/images',  # file/dir/URL/glob, 0 for webcam
        data=ROOT / 'data/coco128.yaml',  # dataset.yaml path
        imgsz=4864,  # inference size (height, width)
        conf_thres=0.25,  # confidence threshold
        iou_thres=0.45,  # NMS IOU threshold
        max_det=1000,  # maximum detections per image
        device='',  # cuda device, i.e. 0 or 0,1,2,3 or cpu
        view_img=False,  # show results
        save_txt=True,  # save results to *.txt
        save_conf=True,  # save confidences in --save-txt labels
        save_crop=False,  # save cropped prediction boxes
        nosave=False,  # do not save images/videos
        classes=None,  # filter by class: --class 0, or --class 0 2 3
        agnostic_nms=False,  # class-agnostic NMS
        augment=False,  # augmented inference
        visualize=False,  # visualize features
        update=False,  # update all models
        project=ROOT / 'runs/detect',  # save results to project/name
        name='exp',  # save results to project/name
        exist_ok=False,  # existing project/name ok, do not increment
        line_thickness=3,  # bounding box thickness (pixels)
        hide_labels=False,  # hide labels
        hide_conf=False,  # hide confidences
        half=False,  # use FP16 half-precision inference
        dnn=False,  # use OpenCV DNN for ONNX inference
):
    source = str(source)
    save_img = not nosave and not source.endswith('.txt')  # save inference images
    is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
    is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
    webcam = source.isnumeric() or source.endswith('.txt') or (is_url and not is_file)
    if is_url and is_file:
        source = check_file(source)  # download

    # Directories
    save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)  # increment run
    (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir

    # Load model
    device = select_device(device)
    model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
    stride, names, pt = model.stride, model.names, model.pt
    imgsz = check_img_size(imgsz, s=stride)  # check image size

    # Dataloader
    if webcam:
        view_img = check_imshow()
        cudnn.benchmark = True  # set True to speed up constant image size inference
        dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt)
        bs = len(dataset)  # batch_size
    else:
        dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt)
        bs = 1  # batch_size
    vid_path, vid_writer = [None] * bs, [None] * bs

    # Run inference
    model.warmup(imgsz=(1 if pt else bs, 3, *imgsz))  # warmup
    seen, windows, dt = 0, [], [0.0, 0.0, 0.0]
    img_Names = os.listdir("fontimg/datav/img1")
    nameindex = 0
    for path, im, im0s, vid_cap, s in dataset:
        print("txt:"+str(img_Names[nameindex]))
        open("txt/uav" + str(img_Names[nameindex]) + ".txt", 'w').close()
        t1 = time_sync()
        im = torch.from_numpy(im).to(device)
        im = im.half() if model.fp16 else im.float()
        im /= 255  # 0 - 255 to 0.0 - 1.0
        if len(im.shape) == 3:
            im = im[None]
        t2 = time_sync()
        dt[0] += t2 - t1

        # Inference
        visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
        pred = model(im, augment=augment, visualize=visualize)
        t3 = time_sync()
        dt[1] += t3 - t2

        # NMS
        pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
        dt[2] += time_sync() - t3

        # Process predictions
        for i, det in enumerate(pred):  # per image
            seen += 1
            if webcam:  # batch_size >= 1
                p, im0, frame = path[i], im0s[i].copy(), dataset.count
                s += f'{i}: '
            else:
                p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)

            p = Path(p)  # to Path
            save_path = str(save_dir / p.name)  # im.jpg
            txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}')  # im.txt
            s += '%gx%g ' % im.shape[2:]  # print string
            gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # normalization gain whwh
            imc = im0.copy() if save_crop else im0  # for save_crop
            annotator = Annotator(im0, line_width=line_thickness, example=str(names))
            if len(det):
                # Rescale boxes from img_size to im0 size
                det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round()

                # Print results
                for c in det[:, -1].unique():
                    n = (det[:, -1] == c).sum()
                    s += f"{n} {names[int(c)]}{'s' * (n > 1)}, "

                # Write results
                for *xyxy, conf, cls in reversed(det):
                    if save_txt:  # Write to file
                        xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh
                        line = (cls, *xywh, conf) if save_conf else (cls, *xywh)  # label format
                        with open(f'{txt_path}.txt', 'a') as f:
                            f.write(('%g ' * len(line)).rstrip() % line + '\n')
                        with open(f"txt/uav" + str(img_Names[nameindex]) + ".txt", 'a') as f:
                            f.write(('%g ' * len(line)).rstrip() % line + '\n')
                    if save_img or save_crop or view_img:  # Add bbox to image
                        c = int(cls)  # integer class
                        label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}')
                        annotator.box_label(xyxy, label, color=colors(c, True))
                    if save_crop:
                        save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True)
            # Stream results
            im0 = annotator.result()
            if view_img:
                if platform.system() == 'Linux' and p not in windows:
                    windows.append(p)
                    cv2.namedWindow(str(p), cv2.WINDOW_NORMAL | cv2.WINDOW_KEEPRATIO)  # allow window resize (Linux)
                    cv2.resizeWindow(str(p), im0.shape[1], im0.shape[0])
            # Save results (image with detections)
            if save_img:
                if dataset.mode == 'image':
                    cv2.imwrite(save_path, im0)
                else:  # 'video' or 'stream'
                    if vid_path[i] != save_path:  # new video
                        vid_path[i] = save_path
                        if isinstance(vid_writer[i], cv2.VideoWriter):
                            vid_writer[i].release()  # release previous video writer
                        if vid_cap:  # video
                            fps = vid_cap.get(cv2.CAP_PROP_FPS)
                            w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                            h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
                        else:  # stream
                            fps, w, h = 30, im0.shape[1], im0.shape[0]
                        save_path = str(Path(save_path).with_suffix('.mp4'))  # force *.mp4 suffix on results videos
                        vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))
                    vid_writer[i].write(im0)
        # Print time (inference-only)
        LOGGER.info(f'{s}Done. ({t3 - t2:.3f}s)')
        drawing(img_Names, nameindex)
        print("txt结束:" + str(img_Names[nameindex]))
        nameindex += 1

    # Print results
    t = tuple(x / seen * 1E3 for x in dt)  # speeds per image
    LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}' % t)
    if save_txt or save_img:
        s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else ''
        LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}{s}")
    if update:
        strip_optimizer(weights[0])  # update model (to fix SourceChangeWarning)


def parse_opt():
    parser = argparse.ArgumentParser()
    parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'best.pt', help='model path(s)')
    parser.add_argument('--source', type=str,
                        default='fontimg/datav/img1/',
                        help='file/dir/URL'
                             '/glob, '
                             '0 for webcam')
    parser.add_argument('--data', type=str, default=ROOT / 'data/aluminium.yaml', help='(optional) dataset.yaml path')
    parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[4864],
                        help='inference size h,w')
    parser.add_argument('--conf-thres', type=float, default=0.6, help='confidence threshold')
    parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold')
    parser.add_argument('--max-det', type=int, default=1000, help='maximum detections per image')
    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
    parser.add_argument('--view-img', action='store_true', help='show results')
    parser.add_argument('--save-txt', default=ROOT / 'txt', action='store_true', help='save results to *.txt')
    parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
    parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes')
    parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
    parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --classes 0, or --classes 0 2 3')
    parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
    parser.add_argument('--augment', action='store_true', help='augmented inference')
    parser.add_argument('--visualize', action='store_true', help='visualize features')
    parser.add_argument('--update', action='store_true', help='update all models')
    parser.add_argument('--project', default=ROOT / 'runs/detect', help='save results to project/name')
    parser.add_argument('--name', default='exp', help='save results to project/name')
    parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
    parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)')
    parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels')
    parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences')
    parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference')
    parser.add_argument('--dnn', action='store_true', help='use OpenCV DNN for ONNX inference')
    opt = parser.parse_args()
    opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1
    print_args(vars(opt))
    return opt


def main(opt):
    check_requirements(exclude=('tensorboard', 'thop'))
    while True:
        time_start = time.time()
        if get_oss():
            time_end_down = time.time()
            run(**vars(opt))
            time_end_recognition = time.time()
            upload_json_file()
            time_end_upload = time.time()
            del_local_img()
            time_end = time.time()
            print("下载图片运行时间：" + str(time_end_down - time_start))
            print("本次程序总运行时间：" + str(time_end - time_start))
        else:
            sleep(20)


if __name__ == "__main__":
    opt = parse_opt()
    main(opt)
